local ffi = require "ffi"

ffi.cdef [[
    typedef struct { float x, y, z, w; } vec4_t;
]]

local vec4_t = ffi.typeof("vec4_t")

local vec4
local mt = {
    ---@param _ vec4
    ---@param x number
    ---@param y number
    ---@param z number
    ---@param w number
    ---@return vec4
    __new = function(_, x, y, z, w)
        if x and y and z and w then
            return ffi.new(vec4_t, { x, y, z, w })
        elseif x and not (y or z or w) then
            return ffi.new(vec4_t, { x, x, x, x })
        elseif not (x or y or z or w) then
            return ffi.new(vec4_t, { 0, 0, 0, 0 })
        end

        error("x, y, z, w must be not a nil")
    end,

    ---@param self vec4
    ---@param other vec4
    ---@return vec4
    __add = function(self, other)
        return vec4(
            self.x + other.x, self.y + other.y, self.z + other.z, self.w + other.w)
    end,

    ---@param self vec4
    ---@param other vec4
    ---@return vec4
    __sub = function(self, other)
        return vec4(
            self.x - other.x, self.y - other.y, self.z - other.z, self.w - other.w)
    end,

    ---@param self vec4
    ---@param other vec4|number
    ---@return vec4
    __mul = function(self, other)
        if type(other) == "number" then
            return vec4(self.x * other, self.y * other, self.z * other, self.w * other)
        end

        return vec4(
            self.x * other.x, self.y * other.y, self.z * other.z * self.w * other.w)
    end,

    ---@param self vec4
    ---@param other vec4|number
    ---@return vec4
    __div = function(self, other)
        if type(other) == "number" then
            return vec4(self.x / other, self.y / other, self.z / other, self.w / other)
        end

        return vec4(
            self.x / other.x, self.y / other.y, self.z / other.z, self.w / other.w)
    end,

    ---@param self vec4
    ---@param other vec4
    __eq = function(self, other)
        return
            self.x == other.x and
            self.y == other.y and
            self.z == other.z and
            self.w == other.w
    end,

    __len = function(_)
        return 4
    end,

    __tostring = function(self)
        return "vec4(" ..
            self.x .. ", " .. self.y .. ", " .. self.z .. "," .. self.w .. ")"
    end,

    ---@param self vec4
    ---@return vec4
    copy = function(self) return vec4(self.x, self.y) end,
}

---@class vec4
---@field x number
---@field y number
---@field z number
---@field w number
---@operator call:vec4
---@operator add:vec4
---@operator sub:vec4
---@operator mul:vec4
---@operator div:vec4
vec4 = ffi.metatype("vec4_t", mt)

return vec4
